#region MIT Licence
/*
 * Copyright (C) 2014 PdfCreative
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom 
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

using PdfCreative.Pdf.Writer.Embedding;
using PdfCreative.Pdf.Writer.Fonts;
using PdfCreative.Pdf.Writer.Images;
using PdfCreative.Pdf.Writer.Utilities;

namespace PdfCreative.Pdf.Writer
{
    /// <summary>
    /// The Writer class represents a stream that can be used to write PDF content to.
    /// </summary>
    /// The Writer class is the main class of the Fishpark PDF writer library. Use the Writer to open a file stream and output
    /// pages, resources and contents to render.
    /// To create PDF documents, create a new Writer instance and specify the name of the output PDF file. Using the Writer directly
    /// allows greater control over content generated, but does require knowledge of the PDF file format.
    public class PdfWriter
    {
        // Private fields
        // ==============
        #region PDF State
        private PdfObjectReference _contentStreamLengthRef;
                                            // The object that will hold the length of the currently open
                                            // content stream.
        private CrossReference _crossReference;
                                            // The CrossReference for this PDF document.
        private PdfObjectReference _currentObject;
                                            // The last object to be created by the Writer
        private PdfPage _currentPage;
                                            // The current page that the Writer is writing to
        private List<PdfResourceReference> _documentResources;
                                            // A list of the resources used by this PDF document

        private Dictionary<string, FontLocation> _fontLocations;
                                            // A list of all font files in the system.

        private List<PdfResourceReference> _pageResources;
                                            // A list of resources used by the current page
        private List<PdfPageTreeEntry> _pages;
                                            // The pages in this document
        private PdfObjectReference _pagesRef;
                                            // Reference to the 'pages' object
        private WriterState _state;
                                            // The current state of the Writer

        private long _streamMarker;         // The start of the current content stream.
        #endregion

        #region Implementation
        private Stream _stream;
                                            // The stream instance that the PDF document is being written to.
        private StreamWriter _streamWriter;
                                            // A stream writer for the PDF document stream
        #endregion



        // Public events
        // =============
        #region Public events
        /// <summary>
        /// A custom resource needs to be written to the PDF stream.
        /// </summary>
        /// The WriteCustomResource event is raised when a custom resource needs to be written to the PDF stream. Custom resources are
        /// created by clients of the Writer class through the CreateCustomResource method and allows usage of resources in content
        /// streams that are not explicitly supported by this version of the Writer.
        /// In order to include these resources, the client must ensure that the definition for the resource is created in the PDF
        /// stream. This can be done by responding to this event or creating the object manually using OpenObject() and
        /// subsequent methods (in which case the event can be ignored)
        /// Failure to include the object in the PDF stream will cause a MissingPdfObject exception when the document is finalised.
        public event CustomResourceEventHandler WriteCustomResource;
        #endregion



        // Public properties
        // =================
        #region Public properties
        /// <summary>
        /// Retrieves a reference to the current page.
        /// </summary>
        public PdfPage CurrentPage
        {
            get { 
                PdfPage page = null;
                
                if(null!=_currentPage)
                    page = (PdfPage)_currentPage.Clone();

                return page;
            }
        }

        /// <summary>
        /// Retrieves the number of pages in this document.
        /// </summary>
        public int PageCount
        {
            get { return CountPages(); }
        }

        /// <summary>
        /// Returns the current state of the PDF writer.
        /// <seealso cref="WriterState"/>
        /// </summary>
        public WriterState State
        {
            get { return _state; }
        }

        /// <summary>
        /// Returns the stream instance that this Writer writes to.
        /// </summary>
        public Stream Stream
        {
            get { return _stream; }
        }

        #endregion



        // Public methods
        // ==============
        #region Public methods
        /// <summary>
        /// Adds the specified resource to the page's resources.
        /// This operation is only valid if a page is currently open.
        /// Calling this method twice with the same resource reference on the same page is safe; the resource will only be added once.
        /// </summary>
        /// <exception cref="ArgumentNullException">The specified resource is a Null reference.</exception>
        /// <exception cref="PdfInvalidOperationException">The specified resource does not exist in the document,
        /// so it cannot be added as a page resource. Either the resource was not created through one of the
        /// resource creation methods (such as CreateFont), or it was created in a different Writer instance.
        /// </exception>
        /// <exception cref="WriterStateException">A page has not been opened. This operation is only valid when a page has been
        /// opened as it applies to the current page.</exception>
        /// <param name="resource">The resource to add</param>
        public void AddResource(PdfResourceReference resource)
        {
            if (null == resource) throw new ArgumentNullException("resource");

            if (WriterState.Free == _state)
                throw new WriterStateException(_state);

            if (!_documentResources.Contains(resource))
                throw new PdfInvalidOperationException("The resource does not exist in this document");
            
            if (!_pageResources.Contains(resource))
                _pageResources.Add(resource);

        } // AddResource()

        /// <summary>
        /// Appends the specified PDF document to this document.
        /// <remarks>
        /// This operation will close the current page, and not open a new page after appending the document
        /// </remarks>
        /// </summary>
        /// <param name="path">The full path to the document that will be appended.</param>
        /// <exception cref="ArgumentNullException">The specified path is a Null reference.</exception>
        /// <exception cref="PdfParserException">An error occurred while parsing the PDF document that is to be embedded.</exception>
        public void AppendPdfDocument(string path)
        {
            if (null == path) throw new ArgumentNullException("path");

            // Close the current page.
            ClosePage();
            _streamWriter.Flush();

            EmbedPdfDocument(path);
        }

        /// <summary>
        /// Applies the specified matrix to the current graphics state. This operation is only valid in the
        /// Content state.
        /// </summary>
        /// <param name="x1y1">The x1y1 element</param>
        /// <param name="x1y2">The x1y2 element</param>
        /// <param name="x2y1">The x2y1 element</param>
        /// <param name="x2y2">The x2y2 element</param>
        /// <param name="x3y1">The x3y1 element</param>
        /// <param name="x3y2">The x3y2 element</param>
        /// <exception cref="WriterStateException">The Writer is not in the Content state. Open a page and a content stream before
        /// calling this method.</exception>
        public void ApplyTransformation(float x1y1, float x1y2, float x2y1, float x2y2, float x3y1, float x3y2)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0:F4} {1:F4} {2:F4} {3:F4} {4:F4} {5:F4} cm", x1y1, x1y2, x2y1, x2y2, x3y1, x3y2);
        }

        /// <summary>
        /// Closes the Writer. This will close the current stream and current page, and then
        /// finalise the document by writing the cross reference tables and document resources such as documents
        /// and images.
        /// </summary>
        /// <exception cref="PdfResourceParseException">A resource definition was not parsed correctly</exception>
        /// <exception cref="WriterCrossReferenceException">A PDF object was created, but was never added to the PDF stream.</exception>
        public void Close()
        {
            ClosePage();

            FinaliseDocument();

            // Flush the stream and close it.
            _streamWriter.Flush();
            _streamWriter.Close();

        }

        /// <summary>
        /// Closes the currently opened object.
        /// </summary>
        public void CloseObject()
        {
            _streamWriter.WriteLine(">>");
            _streamWriter.WriteLine("endobj");
        }

        /// <summary>
        /// Closes the current page. This will close the current content stream if one has been opened, and
        /// write the page resources to the PDF stream. This method only works when the Writer is in the
        /// Page or Content state.
        /// It is not an error to call ClosePage() when no page is open - the call will be ignored.
        /// </summary>
        public void ClosePage()
        {
            // Close the content stream first. If no content stream is available, we need to create one first.
            if(WriterState.Page == _state) OpenContentStream();
            CloseContentStream();

            // If we're in the Page state we write the page resources and close the page.
            if (WriterState.Page == _state)
            {
                WritePageResources();
                _state = WriterState.Free;
            }
        } // ClosePage()

        /// <summary>
        /// Closes the current content stream, if one is open. It is not an error to attempt to close a content
        /// stream if one is not open - the call will simply be ignored.
        /// </summary>
        public void CloseContentStream()
        {
            if (WriterState.Content == _state)
            {
                // Remember the length of the stream contents.
                _streamWriter.Flush();
                long streamLength = _stream.Position - _streamMarker;

                // This ends the stream and the contents.
                _streamWriter.WriteLine("endstream");
                _streamWriter.WriteLine("endobj");

                // Write the object that contains the stream length.
                _streamWriter.Flush();
                _crossReference.Add(_contentStreamLengthRef, _stream.Position+2);

                _streamWriter.WriteLine(
                    "\r\n{0} {1} endobj",
                    _contentStreamLengthRef.ToString(PdfObjectNotation.Declaration),
                    streamLength);

                _state = WriterState.Page;
            }
        } // CloseContentStream()

        /// <summary>
        /// Creates a custom resource in the document. This resource is based on a supplied object reference and allows the object to be
        /// used as a resource in pages.
        /// This object needs to exist in the document and it is the caller's responsibility to do so. This object can be added to the document
        /// at any stage, so it is not required for this object to actually exist in the document when this method is called.
        /// A new name will be created for this resource, and a resource reference returned. This resource reference can
        /// be used in any subsequent content stream of this document.
        /// When the PDF document is finalised, the WriteCustomResource event is raised so that clients can insert objects if they didn't
        /// do this before. Failure to include all objects in the PDF document will result in an exception at the final stage of
        /// document finalisation.
        /// </summary>
        /// <param name="objectRef">The object to add as a resource.</param>
        /// <param name="resourceType">The type of the resource.</param>
        /// <returns>the resource reference</returns>
        /// <exception cref="ArgumentException">The 'objectRef' argument must be a non-empty object.</exception>
        /// <exception cref="ArgumentNullException">The resourceType cannot be null.</exception>
        public PdfResourceReference CreateCustomResource(PdfObjectReference objectRef, string resourceType)
        {
            if (null == objectRef) throw new ArgumentNullException("objectRef");
            if (objectRef.IsEmpty()) throw new ArgumentException("objectRef must be non-empty", "objectRef");
            if (null == resourceType) throw new ArgumentNullException("resourceType");

            CustomResourceReference resourceRef = new CustomResourceReference(
                objectRef, 
                resourceType, 
                string.Format("F{0}", _documentResources.Count + 1));
            
            _documentResources.Add(resourceRef);

            return resourceRef;
        } // CreateCustomResource()

        /// <summary>
        /// Creates a new Font resource and returns a reference that identifies the font.
        /// </summary>
        /// <param name="typename">The font's typename</param>
        /// <returns>The newly created font.</returns>
        /// <exception cref="ArgumentNullException">The 'typename' argument is null</exception>
        public PdfFont CreateFont(string typename)
        {
            return CreateFont(typename, false);
        }

        /// <summary>
        /// Creates a new Font resource and returns a reference that identifies the font.
        /// </summary>
        /// <param name="typename">The font's typename</param>
        /// <param name="embedFontFile">A boolean value that indicates if the font should be embedded inside the
        /// PDF stream (true) or merely referenced.</param>
        /// <returns>Reference to the newly created font.</returns>
        /// <exception cref="ArgumentNullException">The 'typename' argument is null</exception>
        public PdfFont CreateFont(string typename, bool embedFontFile)
        {
            if (null == typename) throw new ArgumentNullException("typename");

            PdfObjectReference fontRef = CreateObjectReference();
            PdfFont font = new PdfFont(fontRef, string.Format("F{0}", _documentResources.Count + 1),typename, embedFontFile);
            font.Location = LookupFont(typename);

            _documentResources.Add(font);

            return font;
        }

        /// <summary>
        /// Creates a new Image resource and returns a reference that identifies the image.
        /// </summary>
        /// <param name="filename">The image filename</param>
        /// <returns>Reference to the newly created image.</returns>
        /// <exception cref="ArgumentNullException">The filename argument is null</exception>
        public PdfImage CreateImage(string filename)
        {
            if (null == filename) throw new ArgumentNullException("filename");

            PdfObjectReference imageRef = CreateObjectReference();
            PdfImage image = new PdfImage(imageRef, string.Format("I{0}", _documentResources.Count + 1), filename);
            _documentResources.Add(image);

            return image;
        }

        /// <summary>
        /// Creates a new object reference that is valid within this document.
        /// The object reference must be used to write an object to the PDF stream at some point, otherwise an exception will
        /// be thrown when the document is finalised.
        /// </summary>
        public PdfObjectReference CreateObjectReference()
        {
            PdfObjectReference newRef = new PdfObjectReference(_currentObject.Id + 1);
            _currentObject = newRef;

            return newRef;
        }

        /// <summary>
        /// Draws an ellipse inside the specified bounding box.
        /// This method is only valid when the Writer is in the Content state.
        /// The ellipse is drawn using the currently selected colour and line styles.
        /// </summary>
        /// <param name="rect">The bounding box of the ellipse.</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void DrawEllipse(PdfRectangle rect)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
           
            // The ellipse is composed of bezier curves. We use a common modifier to create control points for bezier
            // curves that closely resemble ellipses.
            float radiusX = rect.Width/2;
            float radiusY = rect.Height/2;
            float horizontalMidPoint = rect.Left + radiusX;
            float verticalMidPoint = rect.Bottom + radiusY;

            float ellipseModifier = (float) (4*((Math.Sqrt(2) - 1)/3f));
            float horizontalControl = radiusX*ellipseModifier;
            float verticalControl = radiusY*ellipseModifier;

            _streamWriter.WriteLine("{0:F2} {1:F2} m", horizontalMidPoint, rect.Bottom);
            _streamWriter.WriteLine("{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c",
                                    horizontalMidPoint + horizontalControl, rect.Bottom, rect.Right,
                                    verticalMidPoint - verticalControl, rect.Right, verticalMidPoint);
            _streamWriter.WriteLine("{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c", rect.Right,
                                    verticalMidPoint + verticalControl, horizontalMidPoint + horizontalControl,
                                    rect.Top, horizontalMidPoint, rect.Top);
            _streamWriter.WriteLine("{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c",
                                    horizontalMidPoint - horizontalControl, rect.Top, rect.Left,
                                    verticalMidPoint + verticalControl, rect.Left, verticalMidPoint);
            _streamWriter.WriteLine("{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c S", rect.Left,
                                    verticalMidPoint - verticalControl, horizontalMidPoint - horizontalControl,
                                    rect.Bottom, horizontalMidPoint, rect.Bottom);
        } // DrawEllipse()

        /// <summary>
        /// Draws the specified image onto the page. This operation only works in the Content state.
        /// </summary>
        /// <param name="image">The image to draw</param>
        /// <param name="x">The X coordinate to draw the image at</param>
        /// <param name="y">The Y coordinate to draw the image at</param>
        /// <param name="width">The image width</param>
        /// <param name="height">The image height</param>
        /// <exception cref="ArgumentNullException">The image is a Null reference.</exception>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void DrawImage(PdfImage image, float x, float y, float width, float height)
        {
            if(null==image) throw new ArgumentNullException("image");

            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("q");
            _streamWriter.WriteLine("\t{2:F2} 0 0 {3:F2} {0:F2} {1:F2} cm", x, y, width, height);
            _streamWriter.WriteLine("\t/{0} Do", image.Identifier);
            _streamWriter.WriteLine("Q");
        } // DrawImage()

        /// <summary>
        /// Draws a line. This operation only works in the Content state.
        /// The line is drawn using the currently selected colour and line styles.
        /// </summary>
        /// <param name="x1">The X coordinate of the first endpoint of the line</param>
        /// <param name="y1">The Y coordinate of the first endpoint of the line</param>
        /// <param name="x2">The Y coordinate of the second endpoint of the line</param>
        /// <param name="y2">The Y coordinate of the second endpoint of the line</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void DrawLine(float x1, float y1, float x2, float y2)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0:F2} {1:F2} m {2:F2} {3:F2} l S", x1, y1, x2, y2);
        } // DrawLine()

        /// <summary>
        /// Draws a rectangle outline. This operation only works in the Content state.
        /// The rectangle is drawn using the currently selected colour and line styles.
        /// </summary>
        /// <param name="rect">The rectangle to draw</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void DrawRectangle(PdfRectangle rect)
        {
            if (WriterState.Content == _state)
                _streamWriter.WriteLine("{0:F2} {1:F2} {2:F2} {3:F2} re S", rect.Left, rect.Bottom, rect.Width, rect.Height);
            else
                throw new WriterStateException(_state);
        } // DrawRectangle()

        /// <summary>
        /// Fills an ellipse inside the specified bounding box.
        /// This operation only works in the Content state.
        /// The ellipse is filled using the currently selected colour and fill style.
        /// </summary>
        /// <param name="rect">The bounding box of the ellipse.</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void FillEllipse(PdfRectangle rect)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);

            // We create the ellipse using bezier curves, and use a common modifier to create control points that approximate
            // a true ellipse.
            float radiusX = rect.Width/2;
            float radiusY = rect.Height/2;
            float horizontalMidPoint = rect.Left + radiusX;
            float verticalMidPoint = rect.Bottom + radiusY;

            float ellipseModifier = (float) (4*((Math.Sqrt(2) - 1)/3f));
            float horizontalControl = radiusX*ellipseModifier;
            float verticalControl = radiusY*ellipseModifier;

            _streamWriter.WriteLine("{0:F2} {1:F2} m", horizontalMidPoint, rect.Bottom);
            _streamWriter.WriteLine("{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c",
                                    horizontalMidPoint + horizontalControl, rect.Bottom, rect.Right,
                                    verticalMidPoint - verticalControl, rect.Right, verticalMidPoint);
            _streamWriter.WriteLine("{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c", rect.Right,
                                    verticalMidPoint + verticalControl, horizontalMidPoint + horizontalControl,
                                    rect.Top, horizontalMidPoint, rect.Top);
            _streamWriter.WriteLine("{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c",
                                    horizontalMidPoint - horizontalControl, rect.Top, rect.Left,
                                    verticalMidPoint + verticalControl, rect.Left, verticalMidPoint);
            _streamWriter.WriteLine("{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c f", rect.Left,
                                    verticalMidPoint - verticalControl, horizontalMidPoint - horizontalControl,
                                    rect.Bottom, horizontalMidPoint, rect.Bottom);
        } // FillEllipse()

        /// <summary>
        /// Fills a rectangle. This operation only works in the Content state.
        /// The rectangle is filled using the currently selected colour and fill style.
        /// </summary>
        /// <param name="rect">The rectangle to fill</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void FillRectangle(PdfRectangle rect)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0:F2} {1:F2} {2:F2} {3:F2} re f", rect.Left, rect.Bottom, rect.Width, rect.Height);
        } // FillRectangle()

        /// <summary>
        /// Flushes the Pdf writer.
        /// </summary>
        public void Flush()
        {
            _streamWriter.Flush();
        }

        /// <summary>
        /// Retrieves the dimensions of the specified standard page.
        /// The dimensions are returned in a PdfRectangle instance that can be used as
        /// the media box argument to the <see cref="OpenPage()" /> method.
        /// </summary>
        /// <param name="page">The standard page</param>
        /// <returns>The page dimensions</returns>
        public PdfRectangle GetStandardPage(StandardPage page)
        {
            return GetStandardPage(page, Orientation.Portrait);
        }

        /// <summary>
        /// Retrieves the dimensions of the specified standard page.
        /// The dimensions are returned in a PdfRectangle instance that can be used as
        /// the media box argument to the <see cref="OpenPage()" /> method.
        /// </summary>
        /// <param name="page">The standard page</param>
        /// <param name="orientation">The page orientation</param>
        /// <returns>The page dimensions</returns>
        public PdfRectangle GetStandardPage(StandardPage page, Orientation orientation)
        {
            PdfRectangle mediaBox;

            switch(page)
            {
                case StandardPage.A0: 
                    mediaBox = new PdfRectangle(0, 0, 2384, 3370);
                    break;

                case StandardPage.A1:
                    mediaBox = new PdfRectangle(0, 0, 1684, 2384); 
                    break;

                case StandardPage.A2:
                    mediaBox = new PdfRectangle(0, 0, 1191, 1684);
                    break;

                case StandardPage.A3: 
                    mediaBox = new PdfRectangle(0, 0, 842, 1191); 
                    break;

                case StandardPage.A4: 
                default: 
                    mediaBox = new PdfRectangle(0, 0, 595, 842); 
                    break;

                case StandardPage.A5: 
                    mediaBox = new PdfRectangle(0, 0, 420, 595); 
                    break;

                case StandardPage.A6: 
                    mediaBox = new PdfRectangle(0, 0, 298, 420); 
                    break;

                case StandardPage.Legal: 
                    mediaBox = new PdfRectangle(0, 0, 612, 1009); 
                    break;

                case StandardPage.Letter: 
                    mediaBox = new PdfRectangle(0, 0, 612, 791); 
                    break;

                case StandardPage.Tabloid: 
                    mediaBox = new PdfRectangle(0, 0, 791, 1225); 
                    break;
            }

            if(Orientation.Landscape == orientation)
                mediaBox = new PdfRectangle(0,0, mediaBox.Height, mediaBox.Width);

            return mediaBox;
        } // GetStandardPage()

        /// <summary>
        /// Opens a new object and returns its object reference. This operation only works in the Free state.
        /// </summary>
        /// <returns>The object reference</returns>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public PdfObjectReference OpenObject()
        {
            if (WriterState.Free != _state)
                throw new WriterStateException(_state);
            
            PdfObjectReference newRef = CreateObjectReference();
            OpenObject(newRef);

            return newRef;
        } // OpenObject()

        /// <summary>
        /// Opens a new object with the specified object reference as its identifier.
        /// This operation only works in the Free or Page states.
        /// </summary>
        /// <param name="reference">The object's reference</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void OpenObject(PdfObjectReference reference)
        {
            if (WriterState.Content != _state)
            {
                _streamWriter.Flush();

                // Store this object in the cross-reference.
                _crossReference.Add(reference, _stream.Position + 2);
                _streamWriter.WriteLine("\r\n{0}", reference.ToString(PdfObjectNotation.Declaration));
                _streamWriter.WriteLine("<<");
            }
            else
                throw new WriterStateException(_state);
        } // OpenObject()

        /// <summary>
        /// Opens a new object with the specified object reference and type name.
        /// This operation only works in the Free state.
        /// </summary>
        /// <param name="reference">The object reference</param>
        /// <param name="type">The name of the object's type</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void OpenObject(PdfObjectReference reference, string type)
        {
            OpenObject(reference);
            WriteDictionaryEntry("Type", String.Format("/{0}", type));
        } // OpenObject()

        /// <summary>
        /// Opens a new object with the specified type name and returns its reference.
        /// This operation only works in the Free state.
        /// </summary>
        /// <param name="type">The name of the object's type</param>
        /// <returns>The object's reference</returns>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public PdfObjectReference OpenObject(string type)
        {
            if (WriterState.Free != _state)
                throw new WriterStateException(_state);
            
            PdfObjectReference newRef = CreateObjectReference();
            OpenObject(newRef, type);
            return newRef;

        } // OpenObject()

        /// <summary>
        /// Opens a new A4 page. This will close any previously opened page.
        /// </summary>
        /// <returns>A reference to the page object.</returns>
        public PdfPage OpenPage()
        {
            return OpenPage(
                GetStandardPage(StandardPage.A4, Orientation.Portrait),
                Orientation.Portrait);
        }

        /// <summary>
        /// Opens a new page with the specified mediabox size. This will close any previously opened page.
        /// The page will be set up as a portrait page.
        /// </summary>
        /// <param name="mediaBox">The media box of the page. Use the <see cref="GetStandardPage(StandardPage)"/> method to
        /// obtain useful preset media boxes.</param>
        /// <returns>A reference to the page object.</returns>
        public PdfPage OpenPage(PdfRectangle mediaBox)
        {
            return OpenPage(mediaBox, Orientation.Portrait);
        }

        /// <summary>
        /// Opens a new page with the specified mediabox size. This will close any previously opened page.
        /// </summary>
        /// <param name="mediaBox">The media box of the page. Use the <see cref="GetStandardPage(StandardPage)"/> method to
        /// obtain useful preset media boxes.</param>
        /// <param name="display">The orientation of the page. This orientation only affects the way the page is displayed in
        /// PDF viewers such as the Adobe Acrobat reader. It does not affect the page size or the page's coordinate space.
        /// To alter the page orientation, use a media box that represents a landscape or portrait page, as required, or use
        /// the <see cref="GetStandardPage(StandardPage)" /> method to acquire a page with the right orientation.</param>
        /// <returns>A reference to the page object.</returns>
        public PdfPage OpenPage(PdfRectangle mediaBox, Orientation display)
        {
            PdfPage newPage = null;

            // If a current page context exists, close it.
            ClosePage();

            // Create a new page context.
            // We also need to remember how many pages we have created so far.
            PdfObjectReference pageRef = OpenObject("Page");
            PdfObjectReference resourcesRef = CreateObjectReference();
            PdfObjectReference contentsRef = CreateObjectReference();
            newPage = new PdfPage(mediaBox, pageRef, resourcesRef, contentsRef);
            
            // Write the page properties and close the object.
            WriteDictionaryEntry("Parent", _pagesRef);
            WriteDictionaryEntry("MediaBox", mediaBox);
            WriteDictionaryEntry("Resources", resourcesRef);
            WriteDictionaryEntry("Contents", contentsRef);

            switch(display)
            {
                case Orientation.Landscape:
                    WriteDictionaryEntry("Rotate", "-90");
                    break;

                case Orientation.LandscapeUpsideDown:
                    WriteDictionaryEntry("Rotate", "90");
                    break;

                case Orientation.PortraitUpsideDown:
                    WriteDictionaryEntry("Rotate", "180");
                    break;
            }

            CloseObject();

#if TRIAL
            if(null!=_currentPage)
                throw new Pdf.Writer.PdfInvalidOperationException(
                    "The trial version of PDF Graphics can only create a single page. If you have purchased a " +
                    "PDF Graphics licence please follow the instructions in your confirmation e-mail to download " +
                    "A fully functional version of PDF Graphics.");
#endif

            // Add this page to the list and set up internal variables.
            _pages.Add(new PdfPageTreeEntry( pageRef, 1));
            _currentPage = newPage;
            _pageResources.Clear();

            // We are now in the Page state
            _state = WriterState.Page;

            return newPage;
        } // OpenPage

        /// <summary>
        /// Opens a new content stream. This operation is only valid in the Page state. It is an error
        /// to attempt to open a content stream in the Free or Content mode.
        /// </summary>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public PdfObjectReference OpenContentStream()
        {
            // If we have a page context we can open a stream, otherwise we throw an exception.
            if (WriterState.Page != _state)
                throw new WriterStateException(_state);
           
            // Open a new contents object
            OpenObject(_currentPage.ContentsReference);
            _contentStreamLengthRef = CreateObjectReference();
            _streamWriter.WriteLine("\t\t/Length {0}", _contentStreamLengthRef);

            _streamWriter.WriteLine(">>");
            _streamWriter.WriteLine("stream");
            _streamWriter.Flush();
            _streamMarker = _stream.Position;

            // We are now in the Content stream
            _state = WriterState.Content;

            return _currentPage.ContentsReference;

        } // OpenContentStream()

        /// <summary>
        /// Writes an instruction that stores the current graphics state onto the stack.
        /// This operation is only valid in the Content state.
        /// <seealso cref="RestoreGraphicsState"/>
        /// </summary>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void PreserveGraphicsState()
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("q");
        } // PreserveGraphicsState()

        /// <summary>
        /// Writes an instruction to reset the line dash pattern to a solid one.
        /// This operation is only valid in the Content state.
        /// <seealso cref="SetDashPattern"/>
        /// </summary>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void ResetDashPattern()
        {
            if(WriterState.Content!=_state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("[] 0 d");

        } // ResetDashPattern()

        /// <summary>
        /// Writes an instruction that pops a graphics state from the stack.
        /// This operation is only valid in the Content state.
        /// <seealso cref="PreserveGraphicsState"/>
        /// </summary>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void RestoreGraphicsState()
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("Q");
        } // RestoreGraphicsState()

        /// <summary>
        /// Writes an instruction to set the current character spacing.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="spacing">The new value for character spacing</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetCharacterSpacing(float spacing)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0:F2} Tc", spacing);
        } // SetCharacterSpace()

        /// <summary>
        /// Writes an instruction to set the current line dash pattern.
        /// This operation is only valid in the Content state.
        /// <seealso cref="ResetDashPattern"/>
        /// </summary>
        /// <param name="pattern">The pattern</param>
        /// <param name="offset">The offset in the pattern to start at. This value must be 0 or higher</param>
        /// <exception cref="ArgumentNullException">The pattern argument is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The offset argument is less than 0.</exception>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetDashPattern(int[] pattern, int offset)
        {
            if (null == pattern) throw new ArgumentNullException("pattern");
            if (offset < 0) throw new ArgumentOutOfRangeException("offset", "The offset must be at least 0");

            if (WriterState.Content == _state)
                _streamWriter.WriteLine("{0} {1} d", PdfUtilities.GetPdfStringValue(pattern) , offset);
            else
                throw new WriterStateException(_state);
        } // SetDashPattern()

        /// <summary>
        /// Writes an instruction to set the current font ratio as a percentage of normal 1:1 rendering.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="percentage">The percentage that represents the font ratio. A value of 100 restores the ratio back to normal,
        /// and a value of 50 indicates narrow text.</param>
        /// <exception cref="ArgumentOutOfRangeException">The specified percentage is a negative value.</exception>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetFontRatio(int percentage)
        {
            if (percentage < 0) throw new ArgumentOutOfRangeException("percentage", "The percentage can not be negative.");

            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0} Tz", percentage);
        } // SetFontRatio()

        /// <summary>
        /// Writes an instruction to set the current line cap-style.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="style">The line cap-style.</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetLineCapStyle(PdfLineCapStyle style)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0} J", (int) style);
        } // SetLineCapStyle()

        /// <summary>
        /// Writes an instruction to set the current line join-style.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="style">The line join-style.</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetLineJoinStyle(PdfLineJoinStyle style)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0} j", (int) style);
        } // SetLineJoinStyle()

        /// <summary>
        /// Writes an instruction to set the current line width.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="width">The line width.</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetLineWidth(float width)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0:F2} w", width);
        } // SetLineWidth()

        /// <summary>
        /// Writes an instruction to set the current non-stroking colour.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="cyan">The cyan component</param>
        /// <param name="magenta">The magenta component</param>
        /// <param name="yellow">The yellow component</param>
        /// <param name="key">The key component</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetNonStrokeColour(int cyan, int magenta, int yellow, int key)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);

            _streamWriter.WriteLine(
                "{0:F4} {1:F4} {2:F4} {3:F4} k",
                cyan / 255f,
                magenta / 255f,
                yellow / 255f,
                key / 255f);
        } // SetNonStrokeColour()

        /// <summary>
        /// Writes an instruction to set the current non-stroking colour.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="red">The red component</param>
        /// <param name="green">The green component</param>
        /// <param name="blue">The blue component</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetNonStrokeColour(int red, int green, int blue)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0:F4} {1:F4} {2:F4} rg", red/255f, green/255f, blue/255f);
        } // SetNonStrokeColour()

        /// <summary>
        /// Writes an instruction to set the current stroking colour.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="cyan">The cyan component</param>
        /// <param name="magenta">The magenta component</param>
        /// <param name="yellow">The yellow component</param>
        /// <param name="key">The key component</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetStrokeColour(int cyan, int magenta, int yellow, int key)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);

            _streamWriter.WriteLine(
                "{0:F4} {1:F4} {2:F4} {3:F4} K",
                cyan / 255f,
                magenta / 255f,
                yellow / 255f,
                key / 255f);
        } // SetNonStrokeColour()

        /// <summary>
        /// Writes an instruction to set the current stroke colour.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="red">The red component</param>
        /// <param name="green">The green component</param>
        /// <param name="blue">The blue component</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetStrokeColour(int red, int green, int blue)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0:F4} {1:F4} {2:F4} RG", red/255f, green/255f, blue/255f);
        } // SetStrokeColour()

        /// <summary>
        /// Writes an instruction to set the current text rendering mode.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="mode">The text rendering mode.</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetTextRenderingMode(PdfTextRenderingMode mode)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0} Tr", (int) mode);
        } // SetTextRenderingMode()

        /// <summary>
        /// Writes an instruction to set the current text rise.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="rise">The new value for text rise. A value of 0 will reset text rise.</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetTextRise(float rise)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0:F2} Ts", rise);
        } // SetTextRise()

        /// <summary>
        /// Writes an instruction to set the current word spacing.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="spacing">The new value for word spacing</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void SetWordSpacing(float spacing)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0:F2} Tw", spacing);
        } // SetWordSpacing()

        /// <summary>
        /// Writes an instruction to apply a transformation that rotates the coordinate space by the specified angle.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="radians">The angle, in radians, to rotate by</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void TransformRotate(float radians)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine(
                "{0:F4} {1:F4} {2:F4} {3:F4} 0 0 cm",
                Math.Cos(radians), 
                Math.Sin(radians), 
                -Math.Sin(radians), 
                Math.Cos(radians));

        } // TransformRotate()

        /// <summary>
        /// Writes an instruction to apply a transformation that scales the coordinate space by the specified amounts.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="xscale">The scale for the X axis</param>
        /// <param name="yscale">The scale for the Y axis</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void TransformScale(float xscale, float yscale)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("{0:F4} 0 0 {1:F4} 0 0 cm", xscale, yscale);
        } // TransformScale()

        /// <summary>
        /// Writes an instruction to apply a transformation that rotates the coordinate space by the specified angle
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="radiansX">The angle, in radians, to skew the X axis by</param>
        /// <param name="radiansY">The angle, in radians, to skew the Y axis by</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void TransformSkew(float radiansX, float radiansY)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("1 {0:F4} {1:F4} 1 0 0 cm", Math.Tan(radiansX), Math.Tan(radiansY));
        } // TransformSkew()

        /// <summary>
        /// Writes an instruction to apply a transformation that translates the coordinate space by the specified amounts
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="xoff">The amount to translate in the X direction</param>
        /// <param name="yoff">The amount to translate in the Y direction.</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void TransformTranslate(float xoff, float yoff)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            _streamWriter.WriteLine("1 0 0 1 {0:F4} {1:F4} cm", xoff, yoff);
        } // TransformTranslate()

        /// <summary>
        /// Outputs the contents of the specified buffer to the current contents stream.
        /// </summary>
        /// <param name="buffer">The buffer</param>
        /// <param name="start">The index to start writing from.</param>
        /// <param name="length">The number of bytes to write.</param>
        public void WriteBuffer(byte[] buffer, int start, int length)
        {
            _streamWriter.Flush();
            _stream.Write(buffer, start, length);
        } // WriteBuffer()

        /// <summary>
        /// Writes a new dictionary entry with a key and value, to the document.
        /// No checking is done on the validity of the appearance of a dictionary entry at this stage.
        /// The likely use for this method is to add dictionary entries to an object after a call to OpenObject().
        /// </summary>
        /// <param name="key">The entry's key</param>
        /// <param name="value">The entry's value</param>
        public void WriteDictionaryEntry(string key, object value)
        {
            _streamWriter.WriteLine("\t/{0}\t{1}", key, PdfUtilities.GetPdfStringValue(value));
        } // WriteDictionaryEntry()

        /// <summary>
        /// Outputs the specified string directly to the current contents stream. No checking is done on this
        /// string so if it doesn't match the PDF specification, the document will not load.
        /// This method can be called regardless of the current state of the Writer.
        /// </summary>
        /// <param name="content">The content</param>
        public void WriteRawContent(string content)
        {
            _streamWriter.Write(content);
        } // WriteRawContent()

        /// <summary>
        /// Writes the specified text to the document.
        /// This operation is only valid in the Content state.
        /// </summary>
        /// <param name="text">The text to write.</param>
        /// <param name="font">The font to write in</param>
        /// <param name="fontSize">The font size</param>
        /// <param name="left">The X coordinate to write the text at.</param>
        /// <param name="bottom">The Y coordinate to write the text at.</param>
        /// <exception cref="WriterStateException">This operation is not valid in the current state.</exception>
        public void DrawText(string text, PdfFont font, float fontSize, float left, float bottom)
        {
            if (WriterState.Content != _state)
                throw new WriterStateException(_state);
            
            string escapedString = PdfUtilities.EscapeString(text);
            _streamWriter.WriteLine("BT");
            _streamWriter.WriteLine(String.Format("/{0} {1:F2} Tf", font.Identifier, fontSize));
            _streamWriter.WriteLine("{0:F2} {1:F2} Td", left, bottom);
            _streamWriter.WriteLine("({0}) Tj", escapedString);
            _streamWriter.WriteLine("ET");
        } // DrawText()
        #endregion



        // Private implementation
        // ======================
        #region Private implementation
        /// <summary>
        /// Counts the number of pages in this document, and returns the figure.
        /// </summary>
        /// <returns>The number of pages</returns>
        private int CountPages()
        {
            int fullCount = 0;
            foreach (PdfPageTreeEntry entry in _pages) fullCount += entry.PageCount;

            return fullCount;
        }

        /// <summary>
        /// Finalises the document by writing document-wide resources, global objects
        /// and the cross-reference table to the main document.
        /// </summary>
        /// <exception cref="PdfResourceParseException">A font definition was not parsed correctly</exception>
        /// <exception cref="WriterCrossReferenceException">There was an error during the cross reference creation</exception>
        private void FinaliseDocument()
        {
            WriteDocumentResources();
            WritePagesDescriptor();

            long crossReferenceOffset = WriteCrossReference();
            WriteTrailer(crossReferenceOffset);
            _streamWriter.Flush();
        }

        /// <summary>
        /// Initialises the document.
        /// </summary>
        private void InitialiseDocument()
        {
            WriteHeaders();
            WriteInitialObjects();
        }

        /// <summary>
        /// Looks up the specified font. If the font cannot be found, this method returns a Null reference.
        /// </summary>
        /// <param name="typeName">The name of the font.</param>
        /// <returns>The font's location in the system.</returns>
        private FontLocation LookupFont(string typeName)
        {
            System.Diagnostics.Debug.Assert(null != typeName);

            return FontUtilities.LocateFont(typeName);
        } // LookupFont()

        /// <summary>
        /// Writes the custom resources to the PDF stream.
        /// This requires outside intervention because the Writer does not know how to write these
        /// resources.
        /// </summary>
        /// <param name="customResources">The custom resources</param>
        private void WriteCustomResources(IEnumerable<CustomResourceReference> customResources)
        {
            Debug.Assert(null != customResources);

            if(null!=WriteCustomResource)
                foreach(CustomResourceReference reference in customResources) WriteCustomResource(reference);
        }

        /// <summary>
        /// Writes the document resources to the PDF stream.
        /// </summary>
        /// <exception cref="PdfResourceParseException">A font was not parsed correctly.</exception>
        private void WriteDocumentResources()
        {
            List<PdfFont> fonts = new List<PdfFont>();
            List<PdfImage> images = new List<PdfImage>();
            List<CustomResourceReference> customResources = new List<CustomResourceReference>();

            // Copy fonts and images into their respective lists.
            // Custom resources require outside intervention.
            foreach (PdfResourceReference resource in _documentResources)
            {
                if (resource is PdfFont) fonts.Add((PdfFont)resource);
                if (resource is PdfImage) images.Add((PdfImage)resource);
                if (resource is CustomResourceReference) customResources.Add((CustomResourceReference)resource);
            }

            WriteCustomResources(customResources);
            WriteFonts(fonts);
            WriteImages(images);
        } // WriteDocumentResources

        /// <summary>
        /// Writes the binary data of the specified file into the stream.
        /// </summary>
        /// <param name="fileInfo">The file to write</param>
        private void WriteFileData(FileInfo fileInfo)
        {
            Debug.Assert(null != fileInfo);

            byte[] buffer = new byte[64 * 1024];// 64KB buffer
            _streamWriter.Flush();

            FileStream fin = fileInfo.OpenRead();
            int bytesRead = 0;
            do
            {
                bytesRead = fin.Read(buffer, 0, buffer.Length);
                _stream.Write(buffer, 0, bytesRead);
            } while (bytesRead == buffer.Length);
        }

        /// <summary>
        /// Writes the font definition for the specified font to the PDF stream.
        /// </summary>
        /// <param name="font">The font to write</param>
        /// <param name="location">The location of the font files</param>
        /// <exception cref="PdfResourceParseException">Information for the font could not be retrieved</exception>
        private void WriteFont(PdfFont font, FontLocation location)
        {
            Debug.Assert(null != font);
            Debug.Assert(null != location);

            // We attempt to parse the font file information for the specified font.
            FontInfo info = null;
            string subType = string.Empty;
            string fontFileKey = string.Empty;

            try
            {
                switch (location.LocationType)
                {
                    case FontLocationType.TrueTypeFont:
                        subType = "/TrueType";
                        fontFileKey = "FontFile2";
                        info = new TrueTypeFontInfo();
                        info.Parse(Path.Combine(FontUtilities.GetFontPath(), location.OutlinesPath));
                        break;

                    case FontLocationType.Type1Font:
                        subType = "/Type1";
                        fontFileKey = "FontFile";
                        info = new Type1FontInfo();
                        info.Parse(Path.Combine(FontUtilities.GetFontPath(), location.MetricsPath));
                        break;
                }
            }
            // If the font information could not be parsed we throw a PdfResourceParseException and wrap the original exception.
            catch (FontParseException ex)
            {
                throw new PdfResourceParseException(font, ex);
            }

            // If we have some information on the font we proceed to output its PDF objects.
            if (null != info)
            {
                // Write the font header.
                // If the font information has a Postscript name for the font, we use that. Otherwise we stick to the
                // specified name.
                string fontName = info.PostscriptName;
                if (String.IsNullOrEmpty(fontName)) fontName = font.Typename;
                fontName = fontName.Replace(" ", "#20");

                OpenObject(font.ObjectReference, "Font");
                WriteDictionaryEntry("Subtype", subType);
                WriteDictionaryEntry("BaseFont", String.Format("/{0}", fontName));
                WriteDictionaryEntry("Name", String.Format("/{0}", font.Identifier));

                // If this font is not a symbolic font then output the Windows encoding.
                if(4!=info.Flags)
                    WriteDictionaryEntry("Encoding", "/WinAnsiEncoding");

                // Create a new font descriptor reference and add that to the header.
                PdfObjectReference descriptorRef = CreateObjectReference();
                WriteDictionaryEntry("FontDescriptor", descriptorRef);

                // Write the widths of the glyphs that correspond to the characters in the range 32-255.
                const int firstChar = 32;
                const int lastChar = 255;
                WriteDictionaryEntry("FirstChar", firstChar);
                WriteDictionaryEntry("LastChar", lastChar);
                WriteRawContent("\t/Widths [");
                int charsPrinted = 0;

                // Fill a byte array with the numbers, and convert it to unicode numbers.
                // We then look up the character widths for them.
                byte[] bytes = new byte[lastChar-firstChar+1];
                for(int c=firstChar; c<=lastChar; c++) bytes[c-firstChar] = (byte)c;
                char[] characters = System.Text.ASCIIEncoding.Default.GetChars(bytes);

                foreach(char ch in characters)
                {
                    if (0 == charsPrinted % 16) WriteRawContent("\r\n\t\t");
                    
                    // For characters >127, we are interested in the unicode mapping and not the Window ANSI
                    // encoding.
                    WriteRawContent(String.Format("{0} ", info.GetCharacterWidth(ch) * 1000 / info.UnitsPerEm));

                    charsPrinted++;
                }
                WriteRawContent(" ]\r\n");
                CloseObject();

                // We now open the font descriptor object.
                OpenObject(descriptorRef, "FontDescriptor");
                WriteDictionaryEntry("Ascent", (info.Ascent * 1000) / info.UnitsPerEm);
                WriteDictionaryEntry("Descent", (info.Descent * 1000) / info.UnitsPerEm);
                WriteDictionaryEntry("CapHeight", (info.CapHeight * 1000) / info.UnitsPerEm);
                WriteDictionaryEntry("Flags", info.Flags);
                WriteDictionaryEntry("FontBBox", String.Format(
                    "[{0} {1} {2} {3}]",
                    (info.XMin * 1000) / info.UnitsPerEm,
                    (info.YMin * 1000) / info.UnitsPerEm,
                    (info.XMax * 1000) / info.UnitsPerEm,
                    (info.YMax * 1000) / info.UnitsPerEm));
                WriteDictionaryEntry("FontName", String.Format("/{0}", fontName));
                WriteDictionaryEntry("ItalicAngle", info.ItalicAngle);
                WriteDictionaryEntry("StemV", (info.StemV * 1000) / info.UnitsPerEm);

                // If the font is embedded we will add the font file to the stream.
                PdfObjectReference fontProgramRef = PdfObjectReference.Empty;
                if (font.IsEmbedded)
                {
                    fontProgramRef = CreateObjectReference();
                    WriteDictionaryEntry(fontFileKey, fontProgramRef);
                }
                CloseObject();

                // We add the font program now (if neccessary)
                if (false == fontProgramRef.IsEmpty())
                {
                    switch (location.LocationType)
                    {
                        case FontLocationType.TrueTypeFont:
                            WriteFontProgram(font, fontProgramRef, location.OutlinesPath);
                            break;

                        case FontLocationType.Type1Font:
                            WriteType1FontProgram(font, fontProgramRef, location.OutlinesPath);
                            break;
                    }
                }
            }// If we parsed the font successfully
        } // WriteFont()

        /// <summary>
        /// Writes the contents of the font program to the stream.
        /// </summary>
        /// <param name="font">The font to write</param>
        /// <param name="programRef">The reference to the object that we'll create for this.</param>
        /// <param name="fullPath">Full path to the font program.</param>
        private void WriteFontProgram(PdfFont font, PdfObjectReference programRef, string fullPath)
        {
            Debug.Assert(null!=font);
            Debug.Assert(null != programRef);
            Debug.Assert(false == programRef.IsEmpty());
            Debug.Assert(false == String.IsNullOrEmpty(fullPath));

            FileInfo fileInfo = new FileInfo(fullPath);

            OpenObject(programRef);
            WriteDictionaryEntry("Length", fileInfo.Length);
            WriteDictionaryEntry("Length1", fileInfo.Length);
            _streamWriter.WriteLine(">>");
            WriteRawContent("stream\r\n");

            WriteFileData(fileInfo);

            WriteRawContent("endstream\r\n");
            WriteRawContent("endobj\r\n");
        }

        /// <summary>
        /// Writes the font definitions for each of the fonts used in the document.
        /// </summary>
        /// <param name="fonts">The fonts to write.</param>
        private void WriteFonts(IEnumerable<PdfFont> fonts)
        {
            Debug.Assert(null != fonts);

            // We go through the fonts in the list and determine if they are standard fonts or
            // trueType ones. TrueType fonts can be references or embedded fonts.
            foreach (PdfFont font in fonts)
            {
                if (FontUtilities.IsStandardFont(font.Typename))
                    WriteStandardFont(font);
                else
                {
                    try
                    {
                        FontLocation location = LookupFont(font.Typename);

                        if (null != location)
                            WriteFont(font, location);
                        else
                            WriteUnlocatedFont(font);
                    }
                    // If the font could not be located due to a security exception we throw a PdfResourceParseException and include the
                    // original exception for reference.
                    catch (System.Security.SecurityException ex)
                    {
                        throw new PdfResourceParseException(font, ex);
                    }
                }
            } // for each font
        } // WriteFonts()

        /// <summary>
        /// Writes the image definitions for each of the images used in the document.
        /// </summary>
        /// <param name="images">The images to write the definition of.</param>
        /// <exception cref="WriterMissingResourceException">The image could not be found</exception>
        private void WriteImages(IEnumerable<PdfImage> images)
        {
            Debug.Assert(null != images);

            // We go through each of the images in the list, and output them.
            foreach (PdfImage image in images)
            {
                if (false==File.Exists(image.Filename))
                {
                    // The image couldn't be found so we throw an exception
                    throw new WriterMissingResourceException(image);
                }

                FileInfo fileInfo = new FileInfo(image.Filename);

                // Find out what the dimensions of the JPEG are
                ImageInfo imageInfo = null;
                ImageType imageType = ImageType.Unknown;
                try
                {
                    imageType = ImageUtilities.GetImageType(fileInfo);
                    switch (imageType)
                    {
                        case ImageType.Jpeg:
                            imageInfo = new JpegImageInfo();
                            break;

                        case ImageType.Unknown:
                            throw new PdfResourceParseException(image);
                    }

                    if (null != imageInfo)
                        imageInfo.Parse(image.Filename);
                }
                    // If something went wrong during image parsing we throw an exception.
                catch (ImageParseException ex)
                {
                    throw new PdfResourceParseException(image, ex);
                }

                if (null != imageInfo)
                {
                    OpenObject(image.ObjectReference, "XObject");
                    WriteDictionaryEntry("Subtype", "/Image");
                    WriteDictionaryEntry("Width", imageInfo.Width);
                    WriteDictionaryEntry("Height", imageInfo.Height);
                    WriteDictionaryEntry("Length", fileInfo.Length);
                    WriteDictionaryEntry("ColorSpace", String.Format("/{0}", imageInfo.ColourSpace));
                    if (PdfColourSpace.DeviceCMYK == imageInfo.ColourSpace && ImageType.Jpeg == imageType)
                        WriteDictionaryEntry("Decode", new int[] { 1, 0, 1, 0, 1, 0, 1, 0 });

                    WriteDictionaryEntry("BitsPerComponent", 8);
                    WriteDictionaryEntry("Name", String.Format("/{0}", image.Identifier));
                    WriteDictionaryEntry("Filter", "/DCTDecode");
                    _streamWriter.WriteLine(">>");
                    _streamWriter.WriteLine("stream");

                    WriteFileData(fileInfo);

                    _streamWriter.WriteLine("\r\nendstream");
                    _streamWriter.WriteLine("endobj");
                }
            } // for each image
        } // WriteImages()

        /// <summary>
        /// Writes the cross-reference table onto the main stream.
        /// </summary>
        /// <returns>The position of the cross-reference table within the file.</returns>
        private long WriteCrossReference()
        {
            // Get the current stream position.
            _streamWriter.Flush();
            long xrefOffset = _stream.Position;

            _streamWriter.WriteLine("xref");
            _streamWriter.WriteLine("{0} {1}", 0, _currentObject.Id + 1);
            _streamWriter.Flush();
            _crossReference.Write(_stream, 0, _currentObject.Id);
            _streamWriter.Flush();

            // Return the cross-reference position in the file
            return xrefOffset;
        }

        /// <summary>
        /// Writes the PDF headers.
        /// </summary>
        private void WriteHeaders()
        {
            _streamWriter.WriteLine("%PDF-1.7");
            _streamWriter.WriteLine("%iiii");
        }

        /// <summary>
        /// Writes the initial objects to the PDF stream.
        /// </summary>
        private void WriteInitialObjects()
        {
            // Create object references for the catalog and outlines object
            PdfObjectReference catalogRef = CreateObjectReference();
            _pagesRef = CreateObjectReference();
            PdfObjectReference outlinesRef = CreateObjectReference();

            // Open the catalog object
            OpenObject(catalogRef, "Catalog");
            WriteDictionaryEntry("Outlines", outlinesRef);
            WriteDictionaryEntry("Pages", _pagesRef);
            CloseObject();

            // Write the 'outlines' object
            OpenObject(outlinesRef, "Outlines");
            WriteDictionaryEntry("Count", "0");
            CloseObject();
        }

        /// <summary>
        /// Writes the resources used by the current page to the PDF stream.
        /// </summary>
        private void WritePageResources()
        {
            // Open a new contents object
            OpenObject(_currentPage.ResourcesReference);

            _streamWriter.WriteLine("\t/ProcSet\t[/PDF /Text /ImageC]");

            // We need to write each resource type separately so we need to maintain a dictionary of lists.
            Dictionary<string, List<PdfResourceReference>> resourceDictionary = new Dictionary<string, List<PdfResourceReference>>();

            // Copy fonts, images etc. into their respective lists.
            foreach (PdfResourceReference resource in _pageResources)
            {
                // Find the resource type for this resource. If there is no list for this type yet, we add it.
                string resourceType = resource.ResourceType;
                if (false == resourceDictionary.ContainsKey(resourceType))
                    resourceDictionary[resourceType] = new List<PdfResourceReference>();

                // Add the resource to the appropriate list.
                resourceDictionary[resourceType].Add(resource);
            }

            // for each of the different resource types we create a dictionary entry with the resource references as its
            // value. The references are a dictionary in themselves, with the resource identifiers acting as keys.
            foreach (KeyValuePair<string, List<PdfResourceReference>> pair in resourceDictionary)
            {
                if (pair.Value.Count > 0)
                {
                    _streamWriter.Write("\t/{0}\t<<", pair.Key);
                    foreach (PdfResourceReference resource in pair.Value)
                        _streamWriter.Write(" {0}", resource);
                    _streamWriter.WriteLine(">>");
                }                    
            }

            CloseObject();
        } // WritePageResources()

        /// <summary>
        /// Writes the pages descriptor object.
        /// </summary>
        private void WritePagesDescriptor()
        {
            // Get a full count of the pages in this document.
            int fullCount = CountPages();

            OpenObject(_pagesRef, "Pages");
            WriteDictionaryEntry("Kids", _pages);
            WriteDictionaryEntry("Count", fullCount);
            CloseObject();
        }

        /// <summary>
        /// Writes a font definition for a standard font that is used in the document.
        /// </summary>
        /// <param name="font">The font to write.</param>
        private void WriteStandardFont(PdfFont font)
        {
            Debug.Assert(null != font);

            OpenObject(font.ObjectReference, "Font");
            WriteDictionaryEntry("Subtype", "/Type1");
            WriteDictionaryEntry("BaseFont", String.Format("/{0}", font.Typename.Replace(' ','-')));
            WriteDictionaryEntry("Name", String.Format("/{0}", font.Identifier));
            WriteDictionaryEntry("Encoding", "/WinAnsiEncoding");
            CloseObject();
        }

        /// <summary>
        /// Writes the trailer element to the PDF stream.
        /// </summary>
        /// <param name="crossReferenceOffset">The offset, in bytes, of the document's primary cross-reference
        /// table.</param>
        private void WriteTrailer(long crossReferenceOffset)
        {
            _streamWriter.WriteLine("trailer");
            _streamWriter.WriteLine("<<");
            WriteDictionaryEntry("Size", _currentObject.Id + 1);
            WriteDictionaryEntry("Root", new PdfObjectReference(1, 0));
            _streamWriter.WriteLine(">>");

            _streamWriter.WriteLine("\r\nstartxref");
            _streamWriter.WriteLine(crossReferenceOffset.ToString());
            _streamWriter.WriteLine("%%EOF");

        }

        /// <summary>
        /// Writes the contents of the Type1 font program to the stream.
        /// </summary>
        /// <param name="font">The font to write the contents of.</param>
        /// <param name="programRef">The reference to the object that we'll create for this.</param>
        /// <param name="fullPath">Full path to the font program.</param>
        private void WriteType1FontProgram(PdfFont font, PdfObjectReference programRef, string fullPath)
        {
            Debug.Assert(null != font);
            Debug.Assert(null != programRef);
            Debug.Assert(false == programRef.IsEmpty());
            Debug.Assert(false==String.IsNullOrEmpty(fullPath));

            FileInfo fileInfo = new FileInfo(fullPath);

            // We need to find out which portions of the file are the plain text, encrypted and fixed formats.
            try
            {
                Fonts.Type1.Type1FontOutlineFile outlines = new Fonts.Type1.Type1FontOutlineFile();
                outlines.Parse(fullPath);

                OpenObject(programRef);
                WriteDictionaryEntry("Length", fileInfo.Length);
                WriteDictionaryEntry("Length1", outlines.PlainTextLength);
                WriteDictionaryEntry("Length2", outlines.EncryptedLength);
                WriteDictionaryEntry("Length3", fileInfo.Length - outlines.PlainTextLength - outlines.EncryptedLength);
                _streamWriter.WriteLine(">>");
                WriteRawContent("stream\r\n");

                WriteFileData(fileInfo);

                WriteRawContent("endstream\r\n");
                WriteRawContent("endobj\r\n");
            }
            // If the outlines file could not be parsed we throw an exception.
            catch (FontParseException ex)
            {
                throw new PdfResourceParseException(font, ex);
            }
        }

        /// <summary>
        /// Writes a font definition for a font that was not found on the system.
        /// This will be output as a standard 'helvetica' font.
        /// </summary>
        /// <param name="font">The font to write.</param>
        private void WriteUnlocatedFont(PdfFont font)
        {
            Debug.Assert(null != font);

            OpenObject(font.ObjectReference, "Font");
            WriteDictionaryEntry("Subtype", "/Type1");
            WriteDictionaryEntry("BaseFont", "/Helvetica");
            WriteDictionaryEntry("Name", String.Format("/{0}", font.Identifier));
            CloseObject();
        }

        #endregion

        #region Embedding PDF documents
        /// <summary>
        /// Embeds the specified PDF document.
        /// </summary>
        /// <param name="path">Full path to the document</param>
        /// <exception cref="PdfParserException">An error occurred while parsing the PDF document that is to be embedded.</exception>
        private void EmbedPdfDocument(string path)
        {
            Debug.Assert(null != path);

            // Create a new stream to read the file.
            Stream fin = File.OpenRead(path);
            
            // Create an embedder, and start it.
            // The embedder will raise events when it requires assistence.
            PdfEmbedder embedder = new PdfEmbedder(fin, _stream);
            embedder.ContainingPageTreeRoot = _pagesRef;
            embedder.ObjectReferenceRequest += delegate(int count)
            {
                 PdfObjectReference[] refs = new PdfObjectReference[count];
                 for (int n = 0; n < count; n++)
                     refs[n] = CreateObjectReference();

                 return refs;
            };

            Embedding.CrossReference[] xrefs = embedder.Embed();

            // Add all the cross references to the document.
            foreach (Embedding.CrossReference xref in xrefs)
                _crossReference.Add(xref.ObjectReference, xref.Location);

            // Add the top-level page tree to the pages list.
            if (false == embedder.PageTree.IsEmpty())
                _pages.Add(
                    new PdfPageTreeEntry(
                        embedder.PageTree,
                        embedder.PageCount));
        } // EmbedPdfDocument

        #endregion



        // Public constructor
        // ==================
        #region Public constructor
        /// <summary>
        /// Constructs a new Writer instance that writes to the specified path.
        /// This uses a standard 16KB buffer size.
        /// </summary>
        public PdfWriter(string path) : this(File.Create(path, 16384)) { }

        /// <summary>
        /// Constructs a new Writer instance that writes to the specified stream.
        /// </summary>
        /// <param name="stream">The output stream.</param>
        public PdfWriter(Stream stream)
        {
            if (null == stream) throw new ArgumentNullException("stream");
            if (false == stream.CanWrite)
                throw new ArgumentException("The stream must allow writing.", "stream");

            _state = WriterState.Free;
            _stream = stream;
            _streamWriter = new StreamWriter(stream, new System.Text.ASCIIEncoding());
            
            _pages = new List<PdfPageTreeEntry>();
            _crossReference = new CrossReference();
            _currentObject = PdfObjectReference.Empty;
            _documentResources = new List<PdfResourceReference>();
            _fontLocations = new Dictionary<string, FontLocation>();
            _pagesRef = PdfObjectReference.Empty;
            _pageResources = new List<PdfResourceReference>();

            InitialiseDocument();
        }
        #endregion

    } // PdfWriter class
}
